package org.yaac.client.ui;

import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newLinkedList;

import java.util.List;

import org.yaac.client.ui.EditorPropertyManager.PropertyEditCallback;
import org.yaac.shared.SharedConstants;
import org.yaac.shared.SharedConstants.Datastore;
import org.yaac.shared.editor.EntityInfo;
import org.yaac.shared.editor.EntityUpdateInfo;
import org.yaac.shared.editor.PropertyUpdateInfo;
import org.yaac.shared.property.KeyInfo;
import org.yaac.shared.property.NullPropertyInfo;
import org.yaac.shared.property.PropertyInfo;
import org.yaac.shared.property.PropertyType;
import org.yaac.shared.util.StringUtils;


import com.google.gwt.cell.client.ButtonCell;
import com.google.gwt.cell.client.EditTextCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;

/**
 * @author Max Zhu (thebbsky@gmail.com)
 *
 */
public class EditorEntityPanel extends Composite {

	private static EditorEntityPanelUiBinder uiBinder = GWT.create(EditorEntityPanelUiBinder.class);

	interface EditorEntityPanelUiBinder extends UiBinder<Widget, EditorEntityPanel> {
	}

	@UiField(provided=true)
	CellTable<PropertyUpdateInfo> editor;
	
	@UiField
	Button newBtn;
	
	/**
	 * original key
	 */
	private final KeyInfo origKey;
	
	private final EditorPropertyManager editorManager;
	
	private final ListDataProvider<PropertyUpdateInfo> dataProvider;
	
	public EditorEntityPanel(EntityInfo entity, EditorPropertyManager arg1) {
		this.origKey = entity.getKey();
		this.editorManager = arg1;
		
		// init editor
		editor = new CellTable<PropertyUpdateInfo>();
		editor.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);

		{
			final EditTextCell cell = new EditTextCell();
			Column<PropertyUpdateInfo, String> column = new Column<PropertyUpdateInfo, String>(cell) {
				@Override
				public String getValue(PropertyUpdateInfo entry) {
					return isNullOrEmpty(entry.getNewName()) ? entry.getName() : entry.getNewName();
				}
			};
			
			column.setFieldUpdater(new FieldUpdater<PropertyUpdateInfo, String>() {
				@Override
				public void update(int index, PropertyUpdateInfo entry, String value) { 					
					if (isValid(entry, value)) {
						entry.setNewName(value);
						dataProvider.refresh();
					} else {
						// restore old value
						cell.clearViewData(entry);
						editor.redraw();						
					}
				}
				
				private boolean isValid(PropertyUpdateInfo entry, String value) {
					if (Datastore.KEY_RESERVED_NAME.equals(entry.getName()) || 	// check if reserved name __key__ is used 
							Datastore.KEY_RESERVED_NAME.equals(value)) {	
						Window.alert(Datastore.KEY_RESERVED_NAME + " is reserved for key");
						return false;
					} else { // check if the name is duplicated with other entries
						boolean nameConflict = false;
						for (PropertyUpdateInfo e : dataProvider.getList()) {
							if (e != entry) {
								// if the value going to be changed has already been used by other property
								if (StringUtils.sameStr(value, e.getName())) {
									nameConflict = true;
									break;
								}
							}
						}
						
						if (nameConflict) {
							Window.alert(entry.getName() + " is used by other property");
							return false;	
						} else {
							return true;	
						}
					}
				}
			});
			
			editor.addColumn(column, "Property Name");	// TODO : i18n	
		}
		
		// property value
		{
			Column<PropertyUpdateInfo, SafeHtml> column = new Column<PropertyUpdateInfo, SafeHtml>(new SafeHtmlCell()) {
				@Override
				public SafeHtml getValue(PropertyUpdateInfo entry) {
					SafeHtmlBuilder sb = new SafeHtmlBuilder();
					
					if (entry.getInfo() == null) {	// for new property, getInfo will return null 
						sb.appendHtmlConstant("&nbsp;");	// simply blank, doesn't exist
					} else {
						String typeRepresentation = PropertyType.typeOf(entry.getInfo()).getRepresentation();
						sb.appendHtmlConstant("<div title='" + typeRepresentation + "'>");
						sb.appendHtmlConstant(entry.getInfo().asHtml());
						sb.appendHtmlConstant("</div>");	
					}
					
					return sb.toSafeHtml();
				}
			};
			editor.addColumn(column, "Value");	// TODO : i18n
		}
		
		// new value
		{
			Column<PropertyUpdateInfo, SafeHtml> column = new Column<PropertyUpdateInfo, SafeHtml>(new SafeHtmlCell()) {
				@Override
				public SafeHtml getValue(PropertyUpdateInfo entry) {
					SafeHtmlBuilder sb = new SafeHtmlBuilder();
					
					if (entry.isDeleteFlag()) {
						sb.appendHtmlConstant("To be deleted"); // TODO : i18n
					} else if (entry.getNewInfo() == null) {
						sb.appendHtmlConstant("Remain unchanged"); // TODO : i18n
					} else {
						String typeRepresentation = PropertyType.typeOf(entry.getNewInfo()).getRepresentation();
						sb.appendHtmlConstant("<div title='" + typeRepresentation + "'>");
						
						sb.appendHtmlConstant(entry.getNewInfo().asHtml());
						
						sb.appendHtmlConstant("</div>");
					}
					return sb.toSafeHtml();
				}
			};
			editor.addColumn(column, "New Value");	// TODO : i18n
		}
		
		// edit / undo edit
		{
			Column<PropertyUpdateInfo, String> column = new Column<PropertyUpdateInfo, String>(new ButtonCell()) {
				@Override
				public String getValue(PropertyUpdateInfo entry) {
					return "Edit";
				}
			};
			editor.addColumn(column);
			column.setFieldUpdater(new FieldUpdater<PropertyUpdateInfo, String>() {
				@Override
				public void update(int index, final PropertyUpdateInfo e, String value) {
					if (e.isDeleteFlag()) {
						Window.alert("You can't edit a deleted property");
						return;
					}
					
					PropertyEditCallback callback = new PropertyEditCallback() {
						@Override
						public void onEditSuccess(PropertyInfo newInfo) {
							if (e.getInfo() == null) {	// new property, always set newInfo
								e.setNewInfo(newInfo);
							} else if (e.getInfo().equals(newInfo)) {	// existing property, nothing changed, ignore change
								e.setNewInfo(null);	
							} else if (SharedConstants.Datastore.KEY_RESERVED_NAME.equals(e.getName()) &&
									PropertyType.typeOf(newInfo) != PropertyType.KEY) {	// existing property, name conflict, reject
								Window.alert("__key__ property only accept key type");
							}else {	// existing property, something changed
								e.setNewInfo(newInfo);
							}
							dataProvider.refresh();
						}
					};
					
					editorManager.edit(origKey.getKeyString(), 
							e.getNewInfo() == null ? e.getInfo() : e.getNewInfo(), callback);
				}
			});
		}
		
		// delete / undo delete
		{
			Column<PropertyUpdateInfo, String> column = new Column<PropertyUpdateInfo, String>(new ButtonCell()) {
				@Override
				public String getValue(PropertyUpdateInfo entry) {
					if (entry.isDeleteFlag()) {
						return "Undo(X)";
					} else {
						return "X";
					}
				}
			};
			editor.addColumn(column);
			column.setFieldUpdater(new FieldUpdater<PropertyUpdateInfo, String>() {
				@Override
				public void update(int index, final PropertyUpdateInfo e, String value) {
					if (SharedConstants.Datastore.KEY_RESERVED_NAME.equals(e.getName())) {
						Window.alert("__key__ property can't be deleted");
						return;
					}
					
					if (e.isDeleteFlag()) {
						e.setDeleteFlag(false);
					} else {
						e.setDeleteFlag(true);
					}
					dataProvider.refresh();
				}
			});
		}
		
		initWidget(uiBinder.createAndBindUi(this));
		
		// init data
		dataProvider = new ListDataProvider<PropertyUpdateInfo>(PropertyUpdateInfo.fromEntity(entity));
		dataProvider.addDataDisplay(editor);
	}

	public EntityUpdateInfo updatedInfo() {
		List<PropertyUpdateInfo> delta = newLinkedList();
		for(PropertyUpdateInfo update : dataProvider.getList()) {
			if (update.hasChanged()) {
				delta.add(update);
			}
		}
		
		if (delta.isEmpty()) {
			return null;
		} else {
			return new EntityUpdateInfo(this.origKey, delta);
		}
	}

	public KeyInfo getOrigKey() {
		return origKey;
	}
	
	@UiHandler("newBtn")
	void onNewBtnClick(ClickEvent event) {
		String propertyName = null;
		
		do {
			propertyName = 
				Window.prompt("Input new property name(make sure it's not duplicated with existing ones)", 
						"newProperty");
		} while (propertyName != null && isPropertyNameUsed(propertyName));
		
		if (propertyName == null) { // action cancelled
			return;
		} else {	// create new property
			PropertyUpdateInfo newProperty = new PropertyUpdateInfo(propertyName, null);
			newProperty.setNewInfo(new NullPropertyInfo());
			dataProvider.getList().add(newProperty);
			dataProvider.refresh();	
		}
	}

	private boolean isPropertyNameUsed(String propertyName) {
		for (PropertyUpdateInfo e : dataProvider.getList()) {
			if (StringUtils.sameStr(propertyName, e.getName())) {
				return true;
			}
		}
		return false;
	}
}
